package org.ia.efactura;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

/**
 * Clase XmlHandler.
 * @author oscar
 *
 */
public class XmlHandler {
	
	public XmlHandler(){
		System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
	    "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
		
	}
	
	/**
	 * Convierte el objeto de tipo Comprobante en una cadena Xml.
	 * @param comprobante Objeto de tipo Comprobante.
	 * @return Cadena Xml.
	 */
	public String ObjectToXMLString(Comprobante comprobante){
		String xmlString = "";
		Element comprobanteElement = null;
		Document doc = null;
		
		try{
			comprobanteElement = this.ObjectToXML(comprobante);
			doc = new Document(comprobanteElement);
			XMLOutputter serializer = new XMLOutputter(Format.getPrettyFormat());
	    	xmlString = serializer.outputString(doc);
	    	
			
		}catch(Exception e){
			e.printStackTrace();
		}
		return xmlString;
	}
	
	/**
	 * Convierte el objeto Comprobante en una cadena Xml y lo guarda en la ruta especificada.
	 * @param comprobante Objeto de tipo Comprobante.
	 * @param outPath Ruta del archivo de salida.
	 */
	public void ObjectToXMLFile(Comprobante comprobante, String outPath){
		Element comprobanteElement = null;
		Document doc = null;
		
		try{
			comprobanteElement = this.ObjectToXML(comprobante);
			doc = new Document(comprobanteElement);
			XMLOutputter serializer = new XMLOutputter(Format.getPrettyFormat());
		    	java.io.FileWriter writer = new java.io.FileWriter(outPath);
		    	serializer.output(doc, writer);
	            writer.flush();
	            writer.close();
	    	
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	
	/**
	 * Convierte el objeto Comprobante en objeto Element el cual contiene la estructura Xml.
	 * @param comprobante Objeto Comprobante.
	 * @return regresa un objeto de tipo Element que contiene la estructura Xml.
	 */
	@SuppressWarnings("unchecked")
	private Element ObjectToXML(Comprobante comprobante){
		
		//ROOT
		Map comprobanteAttrs = comprobante.getAttributes();
		Element comprobanteElement = setXmlAttributes(comprobanteAttrs, "Comprobante");
		
		//ROOT - EMISOR
		Map emisorAttrs = comprobante.getEmisor().getAttributes();
		Element emisorElement = setXmlAttributes(emisorAttrs, "Emisor");
		comprobanteElement.addContent(emisorElement);
		
		//ROOT - EMISOR - DOM FISCAL
		Map domEmisorAttrs = comprobante.getEmisor().getDomicilio().getAttributes();
		Element domEmisorElement = setXmlAttributes(domEmisorAttrs, "DomicilioFiscal");
		emisorElement.addContent(domEmisorElement);
		
		//ROOT - EMISOR - EXPEDIDO
		Map expEmisorAttrs = comprobante.getEmisor().getExpedido().getAttributes();
		Element expEmisorElement = setXmlAttributes(expEmisorAttrs, "ExpedidoEn");
		emisorElement.addContent(expEmisorElement);
		
		//ROOT - RECEPTOR
		Map receptorAttrs = comprobante.getReceptor().getAttributes();
		Element receptorElement = setXmlAttributes(receptorAttrs, "Receptor");
		comprobanteElement.addContent(receptorElement);
		
		//ROOT - RECEPTOR - DOMICILIO
		Map domReceptorAttrs = comprobante.getReceptor().getDomicilio().getAttributes();
		Element domReceptorElement = setXmlAttributes(domReceptorAttrs, "Domicilio");
		receptorElement.addContent(domReceptorElement);
		
		//ROOT - CONCEPTOS
		Element conceptosElement = new Element("Conceptos");
		comprobanteElement.addContent(conceptosElement);
		
		//ROOT - CONCEPTOS - CONCEPTO
		Vector<Concepto> conceptoVector = comprobante.getConceptos();
		for(Concepto concepto : conceptoVector){
			Map conceptoAttrs = concepto.getAttributes();
			Element conceptoElement = setXmlAttributes(conceptoAttrs, "Concepto");
			Map infoAduaneraAttrs = concepto.getInfoaduanera().getAttributes();
			Element infoaduaneraElement = setXmlAttributes(infoAduaneraAttrs, "InformacionAduanera");
			
			conceptoElement.addContent(infoaduaneraElement);
			conceptosElement.addContent(conceptoElement);
		}
		
		//ROOT - IMPUESTOS
		Element impuestosElement = new Element("Impuestos");
		comprobanteElement.addContent(impuestosElement);
		
		//ROOT - IMPUESTOS - RETENCIONES
		Element retencionesElement = new Element("Retenciones");
		impuestosElement.addContent(retencionesElement);
		
		//ROOT - IMPUESTOS - RETENCIONES - RETENCION
		Vector<Retencion> retencionesVector = comprobante.getRetenciones();
		for(Retencion retencion : retencionesVector){
			Map retencionAttrs = retencion.getAttributes();
			Element retencionElement = setXmlAttributes(retencionAttrs, "Retencion");
			retencionesElement.addContent(retencionElement);
		}
		
		//ROOT - IMPUESTOS - TRASLADOS
		Element trasladosElement = new Element("Traslados");
		impuestosElement.addContent(trasladosElement);
		
		//ROOT - IMPUESTOS - TRASLADOS - TRASLADO
		Vector<Traslado> trasladoVector = comprobante.getTraslados();
		for(Traslado traslado : trasladoVector){
			Map trasladoAttrs = traslado.getAttributes();
			Element trasladoElement = setXmlAttributes(trasladoAttrs, "Traslado");
			trasladosElement.addContent(trasladoElement);
		}
		
		return comprobanteElement;
	}
	
	/*
	private void printXml(Element root){
		Document doc = new Document(root);
	    // serialize it onto System.out
	    try {
	    	
	    	XMLOutputter serializer = new XMLOutputter(Format.getPrettyFormat());
	    	serializer.output(doc, System.out);
	    }
	    catch (IOException e) {
	      System.err.println(e);
	    }
	}
	*/
	
	/*
	private void XmlToFile(Element root){
		Document doc = new Document(root);
	    // serialize it onto System.out
	    try {
	    	
	    	XMLOutputter serializer = new XMLOutputter(Format.getPrettyFormat());
	    //	java.io.FileWriter writer = new java.io.FileWriter("C:\\Documents and Settings\\mtma\\My Documents\\workspace\\eFactura_eclipse\\testme.xml");
	    	java.io.FileWriter writer = new java.io.FileWriter("C:\\Users\\oscar\\workspace\\testws\\src\\testme.xml");
	    	
	    	serializer.output(doc, writer);
            writer.flush();
            writer.close();
	    }
	    catch (IOException e) {
	      System.err.println(e);
	    }
	}
	*/
	
	
	/** Convierte elementos del objeto Comprobante en atributos de un objeto Element.
	 * @param map Contiene los atributos del objeto Comprobante.
	 * @param elementName Nombre que tomara el atributo del objeto Element.
	 * @return regresa un objeto Element con el par atributo-valor.
	 */
	@SuppressWarnings("unchecked")
	private Element setXmlAttributes(Map map, String elementName){
		Element returnElement = new Element(elementName);
		
		Set keys = map.keySet();
		for(Iterator i = keys.iterator(); i.hasNext();){
			String key = (String)i.next();
			String value = (String)map.get(key);
			returnElement.setAttribute(key, value);
		}
		return returnElement;
	}
	
	/**
	 * Regresa la cadena original asociada con una cadena Xml.
	 * @param xmlString Cadena Xml.
	 * @param xsltFilePath Ruta al archivo cadenaOriginal.xsl (proporcionado por el SAT).
	 * @return
	 */
	public String getCadenaOriginal (String xmlString, String xsltFilePath){

			String cadena_original = "";
			try{
			 // Use the static TransformerFactory.newInstance() method to instantiate 
			  // a TransformerFactory. The javax.xml.transform.TransformerFactory 
			  // system property setting determines the actual class to instantiate --
			  // org.apache.xalan.transformer.TransformerImpl.
				TransformerFactory tFactory = TransformerFactory.newInstance();
				
				// Use the TransformerFactory to instantiate a Transformer that will work with  
				// the stylesheet you specify. This method call also processes the stylesheet
			  // into a compiled Templates object.
				Transformer transformador = tFactory.newTransformer(new StreamSource(xsltFilePath));

				// Use the Transformer to apply the associated Templates object to an XML document
				// (foo.xml) and write the output to a file (foo.out).
				Writer outWriter = new StringWriter();
				StreamResult result = new StreamResult(outWriter);
				StreamSource in = new StreamSource(new ByteArrayInputStream(xmlString.getBytes()));
				transformador.transform(in, result);
				
				cadena_original = outWriter.toString();
				
			}catch(Exception e){
				e.printStackTrace();
			}
			return cadena_original;
		}
	
	/**
	 * Valida el archivo Xml proporcionado contra el esquema Xsd.
	 * @param xmlPath Ruta al archivo Xml.
	 * @param xsdPath Ruta al archivo Xsd (proporcionado por el SAT).
	 * @return True si el archivo es valido.
	 */
	public boolean validateXml(String xmlPath, String xsdPath){
		//ref: http://www.edankert.com/apis/jaxp.sax-parser-factory.schema-validation.html
		try {
			  SAXParserFactory factory = SAXParserFactory.newInstance();
			  factory.setNamespaceAware( true);
			  factory.setValidating( true);

			  SAXParser parser = factory.newSAXParser();
			  parser.setProperty( "http://java.sun.com/xml/jaxp/properties/schemaLanguage", 
			                      "http://www.w3.org/2001/XMLSchema");
			  parser.setProperty( "http://java.sun.com/xml/jaxp/properties/schemaSource", 
			                      "file:"+xsdPath);

			  XMLReader reader = parser.getXMLReader();
			  reader.parse( new InputSource( xmlPath));

			} catch ( ParserConfigurationException e) {
			    e.printStackTrace();
				return false;
			} catch ( SAXException e) {
			  e.printStackTrace();
			  return false;
			} catch ( IOException e) {
			  e.printStackTrace();
			  return false;
			}
			return true;
	}	
}


